package org.simpleFramework.core.utils;

import com.bird.constant.Constant;
import com.bird.server.solo.HeadLineService;
import com.bird.server.solo.impl.HeadLineServiceImpl;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileFilter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;


/**
 * @Author: sijunqiang
 * @Date: 2020/4/8 23:21
 * @Description: 获取类的相关工具
 */
@Slf4j
public class ClassUtil {


    /**
     * <P>通过包名获取该包下面所有的class文件</P>
     *
     * @param packageName url   all '.' replace '/'
     */
    public static Set<Class<?>> extractPackageClass(String packageName) {
        /*1: get classLoader */
        ClassLoader classLoader = getClassLoader();
        /*2: get be the load Resource through the classLoader*/
        URL url = classLoader.getResource(packageName.replace(".", "/"));
        if (null == url) {
            log.warn("unable to retrieve anything form package：{}", packageName);
            return null;
        }
        /*3:根据资源的类型不同,采用不同的方式获取资源的集合 */
        Set<Class<?>> classSet = null;
        /*获取协议的名称*/
        String protocol = url.getProtocol();
        //过滤出文件类型的资源(获取出协议文件)
        if (protocol.equalsIgnoreCase(Constant.FILE_PROTOCOL)) {
            classSet = new HashSet<>();
            /*获取文件的实际路径*/
            File packageDirectory = new File(url.getPath());
            extractClassFile(classSet, packageDirectory, packageName);
        }
        //todo  这里也可以校验其他资源
        return classSet;
    }

    /**
     * <P>递归获取目标package里面的所有的class文件(包括package里面的class文件)</P>
     * <note>递归的两大特点 1：需要递进 2：终止</note>
     *
     * @param emptyClassSet 用于装载目标类的集合
     * @param fileSource    文件或者目录
     * @param packageName   包名子
     */
    private static void extractClassFile(Set<Class<?>> emptyClassSet, File fileSource, String packageName) {
        /*终止 如果不是目录话*/
        if (!fileSource.isDirectory()) {
            return;
        }
        /*如果是一个文件夹的话,则调用listFiles方法获取文件下的文件和文件夹(只获取当前文件夹下面的文件和目录)*/
        File[] files = fileSource.listFiles(new FileFilter() {
            /*当为true保存, false舍弃*/
            @Override
            public boolean accept(File file) {
                if (file.isDirectory()) {
                    return true;
                } else {
                    /*获取文件的绝对值路径*/
                    String absoluteFilePath = file.getAbsolutePath();
                    /*若是class文件 就直接加载*/
                    if (absoluteFilePath.endsWith(Constant.CLASS)) {
                        addToClassSet(absoluteFilePath);
                    }
                }
                return false;
            }

            /**
             * <P>根据class文件的绝对值路径, 获取并生成class对象,并放入classSet中</P>
             * @param absoluteFilePath
             */
            private void addToClassSet(String absoluteFilePath) {
                /*从class文件的绝对值路径里提出包含package的类名*/
                /*例如 D:\library\code\simpleFaramwork\target\classes\com\bird\domain\dto\MainPageInfoDTO.class*/
                /*需要弄成com\bird\domain\dto\MainPageInfoDTO  */

                // File.separator 将window的或者linux的路径占位符转换为.
                absoluteFilePath = absoluteFilePath.replace(File.separator, ".");
                /*匹配第一个字母对应下面的索引*/
                int i = absoluteFilePath.indexOf(packageName);
                String className = absoluteFilePath.substring(i);
                /*匹配最后一个字符匹配的s*/
                className = className.substring(0, className.lastIndexOf("."));
                /*通过反射机制获取对应的Class对象并加入classSet里面*/
                Class targetClass = loadClass(className);
                emptyClassSet.add(targetClass);
            }
        });

        /*如果文件不为null 则递归调用*/
        if (files != null) {
            for (File file : files) {
                /*递归调用*/
                extractClassFile(emptyClassSet, file, packageName);
            }
        }
    }


    /**
     * <P>根据包名子获取Class对象</P>
     *
     * @param className class全名= packeage+类名
     * @return Class
     */
    public static Class<?> loadClass(String className) {
        try {
            return Class.forName(className);
        } catch (ClassNotFoundException e) {
            log.error("需要反射的类没有找到", e);
            throw new RuntimeException(e);
        }
    }

    //*******************************************************************************************

    /**
     * <P>获取当前线程的类加载器</P>
     *
     * @return current ClassLoader
     */
    public static ClassLoader getClassLoader() {
        /*get current thread*/
        Thread thread = Thread.currentThread();
        /* get  context classLoader by current thread*/
        ClassLoader contextClassLoader = thread.getContextClassLoader();
        return contextClassLoader;
    }

    //*******************************************************************************************


    /**
     * <P>实例化class</P>
     *
     * @param clazz      Class
     * @param accessible 是否支持创建私有的class对象的实例
     * @param <T>        class的类型
     * @return 类的实例
     */
    public static <T> T newInstance(Class<?> clazz, boolean accessible) {
        try {
            //空参的构造函数
            Constructor<?> constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(accessible);
            return (T) constructor.newInstance();
        } catch (Exception e) {
            log.error("实例化异常", e);
            throw new RuntimeException(e);
        }
    }


    /**
     * <P>给类的成员变量赋值</P>
     *
     * @param field      要赋值的成员变量
     * @param target     要赋值的成员变量所属类的实例
     * @param value      要赋的值
     * @param accessible 是否允许给私有属性进行赋值
     */
    public static void setFieldValue(Field field, Object target, Object value, Boolean accessible) {
        field.setAccessible(accessible);
        try {
            field.set(target, value);
        } catch (IllegalAccessException e) {
            log.warn("setField error", e);
            throw new RuntimeException(e);
        } catch (IllegalArgumentException e) {
            log.error("非法参数异常：", e);
            throw new RuntimeException(e);
        }
    }
}
